﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace FirstFloor.Documents.IO
{
    /// <summary>
    /// An improved version of the WebPackageReader bundling page resources in a single HTTP response.
    /// </summary>
    /// <remarks>
    /// The WebPackageReader2 requires a document service capable of bundling page resources. The  GetDocumentPartHandler in the
    /// open source project Document Toolkit Extensions (http://documenttoolkit.codeplex.com) does provide such implementation.
    /// </remarks>
    public class WebPackageReader2
        : WebPackageReader
    {
        private Dictionary<Uri, ZipFile> partCache = new Dictionary<Uri,ZipFile>();

        /// <summary>
        /// Initializes a new instance of the <see cref="WebPackageReader2"/> class.
        /// </summary>
        /// <param name="packageBaseUri">The package base URI.</param>
        public WebPackageReader2(Uri packageBaseUri)
            : base(packageBaseUri)
        {
        }

        internal ZipFile StoreZip(Stream zipStream)
        {
            var zipFile = new ZipFile(zipStream);

            // cache zip, store on each part uri found in the zip
            foreach (var partUri in zipFile.GetEntryUris()) {
                this.partCache[partUri] = zipFile;
            }

            return zipFile;
        }

        /// <summary>
        /// Gets the list of cached part uris.
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<Uri> GetCachedPartUris()
        {
            return this.partCache.Keys.ToArray();
        }

        private bool TryGetPartStreamFromCache(Uri partUri, out Stream stream)
        {
            stream = null;

            ZipFile zipFile;
            if (this.partCache.TryGetValue(partUri, out zipFile)) {
                stream = zipFile.GetFileStream(partUri);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Initializes a new package part request.
        /// </summary>
        /// <param name="partUri">The part URI.</param>
        /// <returns>The part request.</returns>
        public override IPackagePartRequest CreatePartRequest(Uri partUri)
        {
            if (partUri.IsInterleavingPieceUri()) {
                // interleaving pieces are never returned by WebPackageReader
                return null;
            }

            // try read from cache first
            Stream partStream;
            if (TryGetPartStreamFromCache(partUri, out partStream)) {
                // make sure a copy of the stream is returned, underlying ZIP may be closed at any moment
                return new MemoryPackagePartRequest(partStream.ToMemoryStream());
            }

            // otherwise load from web
            return new WebPackagePartRequest2(this, partUri);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            // close all zip files in cache
            var files = this.partCache.Values.Distinct().ToArray();
            this.partCache.Clear();

            foreach (var file in files) {
                file.Dispose();
            }
        }
    }
}
